<html>
<head>
    <title>vispy.js</title>
    <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">

    <!-- JAVASCRIPT UTILS -->
    <script language="javascript" type="text/javascript" src="jquery-1.11.1.min.js"></script>
    <script language="javascript" type="text/javascript" src="jquery.mousewheel.min.js"></script>
    <script type="text/javascript">

    function viewport(c) {
        c.gl.viewport(0, 0, c.width, c.height);
    }
    
    function clear(c, color) {
        c.gl.clearColor(color[0], color[1], color[2], color[3]);
        c.gl.clear(c.gl.COLOR_BUFFER_BIT);
    }
    
    function init_gl(c) {
        c.gl = c.getContext("webgl") || c.getContext("experimental-webgl");
        viewport(c);
        clear(c, [0,0,0,1]);
    }
    
    function compile_shader(c, type, source) {
        source = "precision mediump float;\n" + source;
        source = source.replace(/\\n/g, "\n")
        
        var shader = c.gl.createShader(c.gl[type]);

        c.gl.shaderSource(shader, source);
        c.gl.compileShader(shader);

        if (!c.gl.getShaderParameter(shader, c.gl.COMPILE_STATUS))
        {
            console.log(c.gl.getShaderInfoLog(shader));
            return null;
        }

        return shader;
    }
    
    
    
    function GLObject() {
        this._handle = -1;
        this._need_create = true;
        this._need_update = true;
        this._need_delete = false;
    }
    GLObject.prototype._activate = function() { }
    GLObject.prototype._deactivate = function() { }
    GLObject.prototype._create = function() { }
    GLObject.prototype._update = function() { }
    GLObject.prototype.activate = function() {
    
        if (this._need_create) {
            this._create();
            this._need_create = false;
        }
        
        this._activate();
        
        if (this._need_update) {
            this._update();
            this._need_update = false;
            this._activate();
        }
    }
    GLObject.prototype.deactivate = function() {
        this._deactivate();
    }
    
    
    
    
    function Buffer(c, data, type) {
        if (typeof(type) == 'undefined')
            type = 'ARRAY_BUFFER'
        this.c = c;
        this.type = type;
        this.set_data(data);
    }
    Buffer.prototype = new GLObject();
    Buffer.prototype.set_data = function(data) {
        this.data = data;
        this.nbytes = data.length;
    }
    Buffer.prototype._create = function() {
        this._handle = this.c.gl.createBuffer();
    }
    Buffer.prototype._delete = function() {
        this.c.gl.deleteBuffer(this._handle);
    }
    Buffer.prototype._activate = function() {
        this.c.gl.bindBuffer(this.c.gl[this.type], this._handle);
    }
    Buffer.prototype._deactivate = function() {
        //this.c.gl.bindBuffer(this.c.gl[this.type], 0);
    }
    Buffer.prototype._update = function() {
        this.c.gl.bufferData(this.c.gl[this.type], this.data, this.c.gl.STATIC_DRAW);
    }
    
    
    
    var gl_typeinfo = {
        'GL_FLOAT':         [1, 'FLOAT', 'float32'],
        'GL_FLOAT_VEC2':    [2, 'FLOAT', 'float32'],
        'GL_FLOAT_VEC3':    [3, 'FLOAT', 'float32'],
        'GL_FLOAT_VEC4':    [4, 'FLOAT', 'float32'],
        'GL_INT':           [1, 'INT', 'int32'],
        'GL_INT_VEC2':      [2, 'INT', 'int32'],
        'GL_INT_VEC3':      [3, 'INT', 'int32'],
        'GL_INT_VEC4':      [4, 'INT', 'int32'],
        'GL_BOOL':          [1, 'BOOL', 'bool'],
        'GL_BOOL_VEC2':     [2, 'BOOL', 'bool'],
        'GL_BOOL_VEC3':     [3, 'BOOL', 'bool'],
        'GL_BOOL_VEC4':     [4, 'BOOL', 'bool']
    }
    
    function Attribute(c, program, name, gtype) {
        this.c = c;
        this.program = program;
        this.name = name;
        this.gtype = gtype;
    }
    Attribute.prototype = new GLObject();
    Attribute.prototype.set_data = function(data) {
        this.data = data;
    }
    Attribute.prototype._create = function() {
        this._handle = this.c.gl.getAttribLocation(this.program._handle, this.name);
    }
    Attribute.prototype._activate = function() {
        this.data.activate();
        r = gl_typeinfo[this.gtype];
        ndim = r[0];
        gtype = r[1];
        dtype = r[2];
        stride = this.data.stride;
        this.size = data.length / ndim;
        
        this.c.gl.enableVertexAttribArray(this._handle);
        this.c.gl.vertexAttribPointer(this._handle, ndim, this.c.gl[gtype],
                                    false, 0, this.data.offset);
    }
    Attribute.prototype._deactivate = function() {
        this.data.deactivate();
    }
    Attribute.prototype._update = function() {
        // TODO
    }
    
    
    
    
    var _ufunctions = {
        'GL_FLOAT':        'uniform1fv',
        'GL_FLOAT_VEC2':   'uniform2fv',
        'GL_FLOAT_VEC3':   'uniform3fv',
        'GL_FLOAT_VEC4':   'uniform4fv',
        'GL_INT':          'uniform1iv',
        'GL_BOOL':         'uniform1iv',
        'GL_FLOAT_MAT2':   'uniformMatrix2fv',
        'GL_FLOAT_MAT3':   'uniformMatrix3fv',
        'GL_FLOAT_MAT4':   'uniformMatrix4fv',
        'GL_SAMPLER_2D':   'uniform1i',
    }
    
    function Uniform(c, program, name, gtype) {
        this.c = c;
        this.program = program;
        this.name = name;
        this.gtype = gtype;
        this._ufunction = _ufunctions[gtype];
    }
    Uniform.prototype = new GLObject();
    Uniform.prototype.set_data = function(data) {
        this.data = data;
        this._need_update = true;
    }
    Uniform.prototype._create = function() {
        this._handle = this.c.gl.getUniformLocation(this.program._handle, this.name);
    }
    Uniform.prototype._activate = function() {
        // TODO texture
    }
    Uniform.prototype._deactivate = function() {
        // TODO texture
    }
    Uniform.prototype._update = function() {
        if (this.data == undefined)
            return;
        this.c.gl[this._ufunction](this._handle, this.data);
    }
    
    
    
    
    
    function Program(c, vertex, fragment) {
        this.c = c;
        this.vertex = compile_shader(c, "VERTEX_SHADER", vertex);
        this.fragment = compile_shader(c, "FRAGMENT_SHADER", fragment);
        this._attributes = {};
        this._uniforms = {};
        this._create();
    }
    Program.prototype = new GLObject();
    Program.prototype._create = function() {
        this._handle = this.c.gl.createProgram();
    }
    Program.prototype._update = function() {
        this.c.gl.attachShader(this._handle, this.vertex);
        this.c.gl.attachShader(this._handle, this.fragment);
        this.c.gl.linkProgram(this._handle);

        if (!this.c.gl.getProgramParameter(this._handle, this.c.gl.LINK_STATUS))
        {
            console.log("Could not initialise shaders");
        }
    }
    Program.prototype.add_attribute = function(name, gtype, data) {
        this._attributes[name] = new Attribute(this.c, this, name, gtype);
        if (typeof(data) != undefined)
            this.set_data(name, data);
    }
    Program.prototype.add_uniform = function(name, gtype, data) {
        this._uniforms[name] = new Uniform(this.c, this, name, gtype);
        if (typeof(data) != undefined)
            this.set_data(name, data);
    }
    Program.prototype._activate = function() {
        if (this._need_update) {
            return;
        }
        this.c.gl.useProgram(this._handle);
        for (var name in this._attributes) {
            this._attributes[name].activate();
        }
        for (var name in this._uniforms) {
            this._uniforms[name].activate();
        }
    }
    Program.prototype._deactivate = function() {
        for (var name in this._attributes) {
            this._attributes[name].deactivate();
        }
        for (var name in this._uniforms) {
            this._uniforms[name].deactivate();
        }
    }
    Program.prototype.set_data = function(name, data) {
        if (name in this._attributes)
            this._attributes[name].set_data(data);
        if (name in this._uniforms)
            this._uniforms[name].set_data(data);
    }
    Program.prototype.draw = function(mode, count) {
        this.activate();
        first = 0;
        count = this._attributes[Object.keys(this._attributes)[0]].size;
        this.c.gl.drawArrays(this.c.gl[mode], first, count);
        this.deactivate();
    }
    
    
    
    function start() {
        var c = document.getElementById('vispy-canvas');
        init_gl(c);
        
        var vertex = 
            "attribute vec2 a_position; " +
            "uniform vec4 u_color; " +
            "void main() { " +
            "    gl_Position = vec4(a_position, 0.0, 1.0);" +
            "}";

         var fragment = 
            "uniform vec4 u_color; " +
            "void main() {" +
            "    gl_FragColor = u_color; " +
            "}";
        
        var prog = new Program(c, vertex, fragment);
        data = new Float32Array([-1., 0., 1., 0]);
        var vbo = new Buffer(c, data);
        prog.add_attribute('a_position', 'GL_FLOAT_VEC2', vbo);
        prog.add_uniform('u_color', 'GL_FLOAT_VEC4', new Float32Array([1., 0., 0., 1.]));
        prog.draw('LINE_STRIP');
        
    }

    </script>
</head>
<body onload="start();">
    <canvas id="vispy-canvas" tabindex="1" width="400" height="400"></canvas>
</body>
</html>